package be.rivendale.mathematics;

import be.rivendale.geometry.AxisAlignedBoundingBox;
import be.rivendale.mathematics.intersection.Intersection;
import be.rivendale.mathematics.intersection.RayIntersectionMode;

/**
 * Represents a <a href="http://en.wikipedia.org/wiki/Triangle">triangle</a> in 3D space.
 * <p>A triangle is represented as a set of three points called a, b and c. All of these points must not
 * be equal, and they must not all lie on the same line.</p>
 * <p>A triangle lies entirely on one plane, and is in fact, much like the {@link Rectangle}, a plane with bounds on
 * it's extent. Because of this a triangle and a {@link Plane} share a lot of mutual functionality.</p>
 */
public class Triangle {
    /**
     * The plane on which this triangle lies.
     */
    private Plane plane;

    /**
     * Defines a triangle by it's three corner points.
     * <p>The order in which these points are passed is importent. This is because it determines the direction of
     * the normal vector (can be pointing to either side of the triangle, see also
     * <a href="http://en.wikipedia.org/wiki/Surface_normal">here</a>). This normal vector later on determines the face
     * orientation of the triangle. (is it facing to the front or to the back?). The direction where the triangle's
     * normal will point to can be determined by the <a href="http://en.wikipedia.org/wiki/Right-hand_rule">right hand
     * rule</a></p>
     * @param a One of the triangle's corner points.
     * @param b One of the triangle's corner points.
     * @param c One of the triangle's corner points.
     */
    public Triangle(Point a, Point b, Point c) {
        this.plane = new Plane(a, b, c);
    }

    /**
     * Retrieves the first corner point of this triangle.
     * @return A corner point of the triangle.
     */
    public Point getA() {
        return plane.getA();
    }

    /**
     * Retrieves the second corner point of this triangle.
     * @return A corner point of the triangle.
     */
    public Point getB() {
        return plane.getB();
    }

    /**
     * Retrieves the thrid corner point of this triangle.
     * @return A corner point of the triangle.
     */
    public Point getC() {
        return plane.getC();
    }

    /**
     * Calculates the normal of this triangle.
     * @return The normal vector of this triangle. A normal vector is a vector that is prependicular to the triangle.
     * The returned vector is <strong>not</strong> normalized. If this
     * is desired, the client should manually call {@link be.rivendale.mathematics.Vector#normalize()} on the returned
     * vector.
     */
    public Vector normal() {
        return plane.normal();
    }

    /**
     * Returns the intersection object for this intersection.
     * <p>The intersection object contains information about a potential intersection. For example
     * if the intersection exists, in what point they intersect, etc...</p>
     * <p>You can expect this to be a relative expensive operation.</p>
     * @param ray The ray to test for intersection with this triangle.
	 * @param rayIntersectionMode The ray intersection mode. This defines the criteria for a successfull intersection test
	 * based on where the ray intersects (relative to it's origin and passthrough point). 
	 * @return The intersection object. Never null, even when there is no intersection.
     */
    public Intersection intersection(Ray ray, RayIntersectionMode rayIntersectionMode) {
        return new Intersection(ray, this, rayIntersectionMode);
    }

	/**
	 * Returns the axis aligned bounding box around this triangle.
	 * @return The axis aligned bounding box around this triangle. 
	 */
	public AxisAlignedBoundingBox getAxisAlignedBoundingBox() {
		return new AxisAlignedBoundingBox(getMinimumBound(), getMaximumBound());
	}

	private Point getMaximumBound() {
		double largestXCoordinate = Math.max(Math.max(getA().getX(), getB().getX()), getC().getX());
		double largestYCoordinate = Math.max(Math.max(getA().getY(), getB().getY()), getC().getY());
		double largestZCoordinate = Math.max(Math.max(getA().getZ(), getB().getZ()), getC().getZ());
		return new Triple(largestXCoordinate, largestYCoordinate, largestZCoordinate);
	}

	private Point getMinimumBound() {
		double smallestXCoordinate = Math.min(Math.min(getA().getX(), getB().getX()), getC().getX());
		double smallestYCoordinate = Math.min(Math.min(getA().getY(), getB().getY()), getC().getY());
		double smallestZCoordinate = Math.min(Math.min(getA().getZ(), getB().getZ()), getC().getZ());
		return new Triple(smallestXCoordinate, smallestYCoordinate, smallestZCoordinate);
	}
}
